package me.itsoo.artemis.framework.repository.executor;

import lombok.extern.slf4j.Slf4j;
import me.itsoo.artemis.framework.repository.config.Configuration;
import me.itsoo.artemis.framework.repository.datasource.ConnectionPool;
import me.itsoo.artemis.framework.repository.exception.UncategorizedSqlException;
import me.itsoo.artemis.framework.repository.executor.handler.ResultTypeHandler;
import me.itsoo.artemis.framework.repository.mapper.MapperDefinition;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;

/**
 * SimpleSqlExecutor
 *
 * @author zxy
 * @version 0.0.1
 * @since 2023/5/11
 */
@Slf4j
public class SimpleSqlExecutor implements SqlExecutor, Transaction {

    private static final ResultTypeHandler HANDLER = new ResultTypeHandler();

    private Configuration config;

    private ConnectionPool dataSource;

    @Override
    public Configuration getConfig() {
        return this.config;
    }

    @Override
    public void setConfig(Configuration config) {
        this.config = config;
        this.dataSource = config.getDataSource();
    }

    @Override
    @SuppressWarnings("squid:S1141")
    public <T> List<T> executeSelect(MapperDefinition md, BoundSql boundSql, Class<T> resultType) {
        try (Connection conn = dataSource.getConnection(true)) {
            try (PreparedStatement ps = conn.prepareStatement(boundSql.getSql())) {
                boundSql.fillPreparedStatementParams(ps);

                final List<T> result = HANDLER.handle(md, ps.executeQuery(), resultType);
                logging(ExecuteType.SELECT, boundSql, result);
                return result;
            } catch (SQLException e) {
                logging(ExecuteType.SELECT, boundSql, e);
                throw e;
            }
        } catch (SQLException e) {
            throw new UncategorizedSqlException(e);
        }
    }

    @Override
    public int executeUpdate(MapperDefinition md, BoundSql boundSql, ExecuteType executeType) {
        // 默认无事务（即自动提交）
        return executeUpdate(md, boundSql, executeType, true);
    }

    @Override
    @SuppressWarnings("squid:S1141")
    public int executeUpdate(MapperDefinition md, BoundSql boundSql, ExecuteType executeType, boolean autoCommit) {
        try (Connection conn = dataSource.getConnection(autoCommit)) {
            try (PreparedStatement ps = conn.prepareStatement(boundSql.getSql())) {
                boundSql.fillPreparedStatementParams(ps);

                final int result = ps.executeUpdate();
                commit(conn);
                logging(executeType, boundSql, result);
                return result;
            } catch (SQLException e) {
                rollback(conn);
                logging(executeType, boundSql, e);
                throw e;
            }
        } catch (SQLException e) {
            throw new UncategorizedSqlException(e);
        }
    }

    @Override
    public void commit(Connection conn) {
        if (Objects.nonNull(conn)) {
            try {
                if (!conn.getAutoCommit()) {
                    conn.commit();
                }
            } catch (SQLException e) {
                throw new UncategorizedSqlException(e);
            }
        }
    }

    @Override
    public void rollback(Connection conn) {
        if (Objects.nonNull(conn)) {
            try {
                if (!conn.getAutoCommit()) {
                    conn.rollback();
                }
            } catch (SQLException e) {
                throw new UncategorizedSqlException(e);
            }
        }
    }

    /**
     * 记录日志
     *
     * @param executeType ExecuteType
     * @param boundSql    BoundSql
     * @param result      Object
     */
    private void logging(ExecuteType executeType, BoundSql boundSql, Object result) {
        Object printResult = result;

        if (log.isDebugEnabled()) {
            if (result instanceof SQLException) {
                printResult = UncategorizedSqlException.getErrorMsg((SQLException) result);
            }

            log.debug("<{}> {}\n{}\nResult: {}",
                    executeType.name(), boundSql.getSql(), Arrays.toString(boundSql.getArgs()), printResult);
        }
    }
}
